import subprocess
import hashlib
import tarfile
import os
import zipfile
import io
import urllib.request
import threading
import socket
import sys
from common.logger import Logger
from common.constants import Constants
from common.exceptions import SuspendException
import win32service
import win32serviceutil
import time
import pywintypes
import shutil
import winreg

class FileDeleteError(Exception):
    pass

class FileNotExistError(Exception):
    pass

class RegValueError(Exception):
    pass

class Common(Constants):

    def __init__(self):
        self.log = Logger(self.LOG_FILE)

    def run_program(self, cmd_line, wait_process_ends = True, shell = True):
        """
        starts the program with arguments
        @param cmd_line: command line to be started
        @param wait_process_ends: wait while process ends
        @param shell: True if start in the shell
        @return: [exit_code, stdout, stderr lines list objects]    
        """    
        #it's bit ugly but it's a workaround to issue4112 and other subprocess pipe issues
        if wait_process_ends:
            fout = open(self.OUT_FILE_NAME,'w')
            ferr = open(self.ERR_FILE_NAME,'w')
        else:
            fout, ferr, out, err = None, None, None, None

        pipe = subprocess.Popen(cmd_line, shell, stdout = fout, stderr = ferr)
        if wait_process_ends:
            return_code = pipe.wait()
        else:
            return_code = pipe.poll()
        if wait_process_ends:
            fout.close()
            ferr.close()

            fout = open(self.OUT_FILE_NAME,'r')
            ferr = open(self.ERR_FILE_NAME,'r')

            err = ferr.readlines()
            out = fout.readlines()    

            ferr.close()
            fout.close()

        if os.path.exists(self.OUT_FILE_NAME) and wait_process_ends: os.unlink(self.OUT_FILE_NAME)
        if os.path.exists(self.ERR_FILE_NAME) and wait_process_ends: os.unlink(self.ERR_FILE_NAME)

        return [return_code, out, err]

    def read_file_content(self, file_name, lines_list = False):
        """
        reads file content
        @param file_name: name of the file
        @lines_list: sets the method of returning file content: list or string
        @return: content of the file
        """
        if self.is_file_exist(file_name):
            fp = open(file_name)
            if lines_list:
                content = [f for f in fp]
            else:
                content = fp.read()
            fp.close()
            return content
    def del_file(self, file_name):
        """
        deletes file content
        @param file_name: name of the file
        """
        self.log.debug('deleting file: {0}'.format(file_name))
        os.unlink(file_name)

    def is_file_exist(self, file_name):
        """
        checks if given file exists
        @param file_name: name of the file
        @return: bool
        """        
        return os.path.exists(file_name)

    def get_md5(self, file_name):
        """
        defines md5 string of the filename
        @param file_name: path to the file which sha1 sum should to be calculated for
        @return: sha1
        """
        buff = 2048
        m = hashlib.md5()	    
        data_list = []
        size = os.path.getsize(file_name)	    
        fp = open(file_name,'rb')    
        while size > 0:    
            data = fp.read(buff)
            size -= buff
            data_list.append(data)
        fp.close()
        block = "".join(data_list)    	    
        m.update(block)
        del block
        return m.hexdigest()

    def untar_archive(self, file_name):
        """
        extracts .tar.gz archive    
        @param file_name: name of the archive
        @return: directory name with the unpacked stuff
        """
        dir_name = file_name.replace('.tar.gz', '')

        if tarfile.is_tarfile(file_name):
            tar = tarfile.open(file_name)
            tar.extractall()
            tar.close()

            return dir_name    

    def get_stream(self, stream_subprocess_object):
        """
        used to detect was something passed to stderr/stdout    
        @param stream_subprocess_object: subprocess stderr/stdout object    
        @return: [stderr/stdout lines]
        """
        if stream_subprocess_object is None:
            out = []
        else:
            out = stream_subprocess_object.readlines()	
        return out

    def get_domain_name(self):
        """
        return domain name of the server
        """
        return ".".join(socket.getfqdn().split('.')[1:])

    def get_host_ip(self):
        """
        detects host ip address

        @return: ip address
        """

        return socket.gethostbyname_ex(socket.gethostname())[2][1]

    def get_host_address(self):
        """
        detects host address

        @return: host address
        """
        return socket.gethostname()

    def create_file(self, file_name, lines_list):
        """
        creates file with the given content    
        @param file_name: name of the file to create
        @param lines_list: [content of the file - list of lines]
        """
        fp = open(file_name,'w')
        fp.writelines(lines_list)
        fp.close()

    def download_file_http(self, url):
        """
        downloads file from the http link
        @param url: download link url
        @return file_name: name of the downloaded file
        """
        file_name = os.path.basename(url)
        urllib.request.urlretrieve(url, file_name)
        return file_name

    def read_file_http(self, url, timeout = 0):
        """
        reads file from http link (unsafe)
        @param url: file url\
        @param timeout: use timeout(seconds) for wait response, 0 -default means infinite wait attempt.
        @return: file content
        """
        if timeout is 0:
            args = (url,)
        else:
            args = (url, None, timeout)
        return urllib.request.urlopen(*args).read().strip().decode()				

    def unzip(self, file_to_unzip, path_to_unzip = '.'):
        """
        unzips file to the current directory - only looks for first file in the archive
        @param file_to_unzip: archive that should to be unpacked
        @param path_to_unzip: path where unzip the file to
        """
        if not self.is_file_exist(path_to_unzip):
            raise FileNotExistError('no {0} found'.format(path_to_unzip))
        original = open(file_to_unzip, 'rb')
        try:
            data = original.read()
        finally:
            original.close()
        position =  data.rfind(zipfile.stringEndArchive)
        coredata = io.BytesIO(data[: 22 + position])

        zip = zipfile.ZipFile(coredata)
        name_list = zip.namelist()

        for file in name_list:
            file_path = os.path.join(path_to_unzip, file).replace('/', '\\')
            if not os.path.exists(os.path.dirname(file_path)):				
                os.makedirs(os.path.dirname(file_path))
                if file.endswith('/'):
                    os.mkdir(os.path.basename(file))
                else:
                    open(file_path, 'wb').write(zip.read(file))
            else:
                if file.endswith('/'):
                    os.mkdir(os.path.basename(file))
                else:
                    open(file_path, 'wb').write(zip.read(file))									

    def stop_service(self, service_name, timeout = 60):
        """
        stops service and waits while it stops
        @param service_name: name of the service
        @param timeout: timeout 
        """        
        if win32serviceutil.QueryServiceStatus(service_name)[1] == win32service.SERVICE_RUNNING:
            self.log.debug('stopping service: {0}'.format(service_name))
            for i in range(3):
                try:
                    if win32serviceutil.QueryServiceStatus(service_name)[1] == win32service.SERVICE_STOPPED:
                        break
                    win32serviceutil.StopService(service_name)
                    win32serviceutil.WaitForServiceStatus(service_name, win32service.SERVICE_STOPPED, timeout)
                except pywintypes.error:
                    self.log.debug('can\'t stop service: {0}'.format(service_name))
                    time.sleep(timeout)
        self.log.debug('service stopped: {0}'.format(service_name))

    def start_service(self, service_name, timeout = 60):
        """
        start service and waits while it starts
        @param service_name: name of the service
        @param timeout: timeout 
        """
        if win32serviceutil.QueryServiceStatus(service_name)[1] == win32service.SERVICE_STOPPED:
            self.log.debug('starting service: {0}'.format(service_name))
            for i in range(3):
                try:
                    if win32serviceutil.QueryServiceStatus(service_name)[1] == win32service.SERVICE_RUNNING:
                        break
                    win32serviceutil.StartService(service_name)
                    win32serviceutil.WaitForServiceStatus(service_name, win32service.SERVICE_RUNNING, timeout)
                except pywintypes.error:
                    self.log.debug('can\'t start service: {0}'.format(service_name))
                    time.sleep(timeout)
        self.log.debug('service started: {0}'.format(service_name))

    def get_reg_value(self,reg_part, reg_key_value_name):
        """
        gets the specified key value from registry
        @param ret_part: acronym name of the registry part (for ex. 'HKLM')
        @param regKeyValueName: registery key value to be fetched
        @return: registry value
        """
        if reg_part == 'HKCR':
            reg_handle = winreg.HKEY_CLASSES_ROOT
        elif reg_part == 'HKCU':
            reg_handle = winreg.HKEY_CURRENT_USER
        elif reg_part == 'HKLM':
            reg_handle = winreg.HKEY_LOCAL_MACHINE
        elif reg_part == 'HKU':
            reg_handle = winreg.HKEY_USERS
        elif reg_part == 'HKCC':
            reg_handle = winreg.HKEY_USERS
        else :
            assert RegValueError("registry part is unknown: " + reg_part)       
        i = reg_key_value_name.rfind("\\")
        if i < 0:
            assert RegValueError("reg path specified is empty")
        value_name = reg_key_value_name[i+1:]
        key_name = reg_key_value_name[:i]
        skey = winreg.OpenKey(reg_handle, key_name)
        (out, val_type) = winreg.QueryValueEx(skey, value_name)
        winreg.CloseKey(skey)            
        return out
    
    def is_x86(self):
        """
        function checks if it's x86 architecture
        """
        return 'x86' in os.environ['PROCESSOR_ARCHITECTURE']

